
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8" />
  <title>Vant2 拖拽装修 Demo（增强版）</title>
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <script src="/static/lib/vue2/vue.min.js"></script>
  <script src="/static/lib/vant2/vant.min.js"></script>
  <link rel="stylesheet" href="/static/lib/vant2/index.css" />
  <style>
    body {font-family: 'Helvetica Neue', Arial, sans-serif;margin: 0;padding: 0;background-color: #f5f5f5;}
    .container {max-width: 1400px;margin: 0 auto;background: white;box-shadow: 0 2px 12px rgba(0,0,0,0.1);}
    .header {background: linear-gradient(135deg, #3f45ff, #00c9ff);color: white;padding: 20px;text-align: center;}
    .main-content {display: flex;min-height: 600px;}
    .sidebar {width: 250px;background: #f8f9fa;padding: 20px;border-right: 1px solid #eee;overflow-y: auto;}
    .canvas-area {flex: 1;padding: 20px;background: #f0f2f5;overflow: auto;}
    .control-panel {background: white;padding: 15px;border-radius: 8px;margin-bottom: 20px;display: flex;flex-wrap: wrap;gap: 10px;box-shadow: 0 2px 8px rgba(0,0,0,0.06);}
    .control-panel button {padding: 8px 16px;background: linear-gradient(135deg, #3f45ff, #00c9ff);color: white;border: none;border-radius: 20px;cursor: pointer;font-size: 14px;transition: all 0.3s;}
    .control-panel button:hover {transform: translateY(-2px);box-shadow: 0 4px 12px rgba(63,69,255,0.3);}
    .widget-item {margin: 10px 5px;padding: 12px 15px;background: white;border-radius: 8px;cursor: move;border: 1px solid #ebedf0;transition: all 0.3s;font-size: 14px;display: flex;align-items: center;box-shadow: 0 2px 5px rgba(0,0,0,0.05);}
    .widget-item .icon {margin-right: 10px;width: 24px;height: 24px;display: flex;align-items: center;justify-content: center;background: #f0f2f5;border-radius: 4px;color: #3f45ff;}
    .vue-grid-layout {background: #fafafa;border-radius: 8px;min-height: 500px;position: relative;border: 1px solid #ebedf0;}
    .vue-grid-item {background: white;border-radius: 8px;box-shadow: 0 2px 8px rgba(0,0,0,0.08);position: absolute;overflow: hidden;transition: all 0.3s ease;}
    .vue-grid-item.selected {outline: 2px solid #3f45ff; box-shadow: 0 0 0 2px rgba(63,69,255,0.2) inset;}
    .delete-btn {position: absolute;top: 5px;right: 5px;background: #ff6b6b;color: white;border: none;width: 24px;height: 24px;border-radius: 50%;cursor: pointer;font-size: 14px;display: flex;align-items: center;justify-content: center;z-index: 10;}
    .resizer {position: absolute;right: 0;bottom: 0;width: 16px;height: 16px;background: #3f45ff;cursor: se-resize;transform: rotate(45deg);z-index: 10;}
    .properties-panel {background: white;padding: 15px;border-radius: 8px;margin-top: 20px;box-shadow: 0 2px 8px rgba(0,0,0,0.06);}
    .properties-panel h4 {margin: 0 0 10px 0;}
    .properties-panel .group {margin-bottom: 12px;}
    .properties-panel label {display: block;font-size: 12px;color: #666;margin-bottom: 6px;}
    .properties-panel input, .properties-panel select, .properties-panel textarea {width: 100%;box-sizing: border-box;padding: 8px;border: 1px solid #ebedf0;border-radius: 6px;background: #fafafa;}
    .properties-panel .grid-2 {display: grid;grid-template-columns: 1fr 1fr;gap: 10px;}
    .grid-item-config {background: #f8f9fa;padding: 10px;border-radius: 6px;margin-bottom: 8px;border: 1px solid #ebedf0;}
    .export-modal {position: fixed;top: 0;left: 0;width: 100%;height: 100%;background: rgba(0,0,0,0.7);display: flex;align-items: center;justify-content: center;z-index: 1000;}
  </style>
</head>
<body>
  <div id="app" @keydown="handleKeydown" tabindex="0">
    <div class="container">
      <div class="header"><h2>📱 Vant2 拖拽页面生成器（增强版）</h2></div>
      <div class="main-content">
        <div class="sidebar">
          <h4>📦 组件库</h4>
          <div class="widget-category">
            <h5>基础组件</h5>
            <div v-for="w in basicWidgets" :key="w.type" class="widget-item" draggable="true" @dragstart="onWidgetDragStart($event, w)"><span class="icon">{{ w.icon }}</span><span>{{ w.text }}</span></div>
          </div>
          <div class="widget-category">
            <h5>表单组件</h5>
            <div v-for="w in formWidgets" :key="w.type" class="widget-item" draggable="true" @dragstart="onWidgetDragStart($event, w)"><span class="icon">{{ w.icon }}</span><span>{{ w.text }}</span></div>
          </div>
          <div class="widget-category">
            <h5>反馈组件</h5>
            <div v-for="w in feedbackWidgets" :key="w.type" class="widget-item" draggable="true" @dragstart="onWidgetDragStart($event, w)"><span class="icon">{{ w.icon }}</span><span>{{ w.text }}</span></div>
          </div>
          <div class="widget-category">
            <h5>展示组件</h5>
            <div v-for="w in displayWidgets" :key="w.type" class="widget-item" draggable="true" @dragstart="onWidgetDragStart($event, w)"><span class="icon">{{ w.icon }}</span><span>{{ w.text }}</span></div>
          </div>
        </div>
        <div class="canvas-area">
          <div class="control-panel">
            <button @click="saveLayout">💾 保存布局</button>
            <button @click="loadLayout">📂 加载布局</button>
            <button @click="clearLayout">🗑️ 清空画布</button>
            <button @click="exportLayout">📤 导出代码</button>
            <button @click="exportJSON">💾 导出 JSON</button>
            <input type="file" @change="importJSON" accept=".json" style="display:none" ref="jsonInput">
            <button @click="$refs.jsonInput.click()">📂 导入 JSON</button>
          </div>
          <div ref="gridLayout" class="vue-grid-layout" @dragover.prevent @drop="handleDrop" @click="clearSelection">
            <div v-for="item in layout" :key="item.i" class="vue-grid-item" :class="{selected: item.i===selectedId}" :style="{left:(item.x/12*100)+'%',top:(item.y*rowHeight)+'px',width:(item.w/12*100)+'%',height:(item.h*rowHeight)+'px'}" @click.stop="selectItem(item.i)">
              <div class="grid-item-wrapper"><div class="drag-item">
                <van-card v-if="item.type==='card'" :title="item.title" :desc="item.desc" :price="item.price" :thumb="item.thumb" currency="¥"></van-card>
                <van-button v-else-if="item.type==='button'" :type="item.buttonType" :size="item.buttonSize">{{item.text}}</van-button>
                <van-image v-else-if="item.type==='image'" :src="item.src" :fit="item.fit"></van-image>
                <van-cell-group v-else-if="item.type==='cell'"><van-cell v-for="(cell, index) in item.cells" :key="index" :title="cell.title" :icon="cell.icon" :url="cell.url" is-link/></van-cell-group>
                <van-notice-bar v-else-if="item.type==='notice'" :text="item.text" :color="item.color"></van-notice-bar>
                <van-field v-else-if="item.type==='field'" v-model="item.value" :label="item.label" :placeholder="item.placeholder"></van-field>
                <van-switch v-else-if="item.type==='switch'" v-model="item.checked"></van-switch>
                <van-swipe v-else-if="item.type==='swipe'" :autoplay="item.autoplay"><van-swipe-item v-for="(img,i) in item.images" :key="i"><img :src="img" style="width:100%"></van-swipe-item></van-swipe>
                <van-grid v-else-if="item.type==='grid'" :column-num="item.cols"><van-grid-item v-for="(g,i) in item.items" :key="i" :icon="g.icon" :text="g.text" :url="g.url"/></van-grid>
                <van-empty v-else-if="item.type==='empty'" :description="item.text"></van-empty>
                <van-loading v-else-if="item.type==='loading'" :type="item.loadingType" :size="item.size"></van-loading>
                <van-row v-else-if="item.type==='row'"><van-col span="12" style="text-align:center;">左侧</van-col><van-col span="12" style="text-align:center;">右侧</van-col></van-row>
              </div>
              <button class="delete-btn" @click.stop="removeItem(item.i)">×</button></div>
            </div>
          </div>
          <div class="properties-panel" v-if="selectedItem">
            <h4>🛠 属性设置</h4>
            <div class="group">
              <label>组件类型</label>
              <input type="text" :value="selectedItem.type" disabled>
            </div>
            <div class="group grid-2">
              <div>
                <label>X(栅格 0-11)</label>
                <input type="number" v-model.number="selectedItem.x" @change="clampPositionSize(selectedItem)">
              </div>
              <div>
                <label>Y(行 >=0)</label>
                <input type="number" v-model.number="selectedItem.y" @change="clampPositionSize(selectedItem)">
              </div>
            </div>
            <div class="group grid-2">
              <div>
                <label>宽度W(1-12)</label>
                <input type="number" v-model.number="selectedItem.w" @change="clampPositionSize(selectedItem)">
              </div>
              <div>
                <label>高度H(>=1)</label>
                <input type="number" v-model.number="selectedItem.h" @change="clampPositionSize(selectedItem)">
              </div>
            </div>

            <div v-if="selectedItem.type==='button'">
              <div class="group"><label>文本</label><input type="text" v-model="selectedItem.text"></div>
              <div class="group grid-2">
                <div><label>类型</label>
                  <select v-model="selectedItem.buttonType">
                    <option value="default">default</option>
                    <option value="primary">primary</option>
                    <option value="success">success</option>
                    <option value="danger">danger</option>
                    <option value="warning">warning</option>
                    <option value="info">info</option>
                  </select>
                </div>
                <div><label>尺寸</label>
                  <select v-model="selectedItem.buttonSize">
                    <option value="normal">normal</option>
                    <option value="large">large</option>
                    <option value="small">small</option>
                    <option value="mini">mini</option>
                  </select>
                </div>
              </div>
            </div>

            <div v-else-if="selectedItem.type==='card'">
              <div class="group"><label>标题</label><input type="text" v-model="selectedItem.title"></div>
              <div class="group"><label>描述</label><textarea rows="2" v-model="selectedItem.desc"></textarea></div>
              <div class="group grid-2"><div><label>价格</label><input type="text" v-model="selectedItem.price"></div><div><label>缩略图</label><input type="text" v-model="selectedItem.thumb"></div></div>
            </div>

            <div v-else-if="selectedItem.type==='image'">
              <div class="group"><label>图片地址</label><input type="text" v-model="selectedItem.src"></div>
              <div class="group"><label>适应</label>
                <select v-model="selectedItem.fit">
                  <option value="contain">contain</option>
                  <option value="cover">cover</option>
                  <option value="fill">fill</option>
                  <option value="none">none</option>
                  <option value="scale-down">scale-down</option>
                </select>
              </div>
            </div>

            <div v-else-if="selectedItem.type==='notice'">
              <div class="group"><label>文本</label><input type="text" v-model="selectedItem.text"></div>
              <div class="group"><label>颜色</label><input type="color" v-model="selectedItem.color"></div>
            </div>

            <div v-else-if="selectedItem.type==='field'">
              <div class="group"><label>标签</label><input type="text" v-model="selectedItem.label"></div>
              <div class="group"><label>占位</label><input type="text" v-model="selectedItem.placeholder"></div>
              <div class="group"><label>值</label><input type="text" v-model="selectedItem.value"></div>
            </div>

            <div v-else-if="selectedItem.type==='switch'">
              <div class="group"><label>开关</label><select v-model="selectedItem.checked"><option :value="true">开</option><option :value="false">关</option></select></div>
            </div>

            <div v-else-if="selectedItem.type==='swipe'">
              <div class="group"><label>图片(逗号分隔)</label><textarea rows="2" :value="selectedItem.images.join(',')" @change="onChangeImages($event, selectedItem)"></textarea></div>
              <div class="group"><label>自动播放(ms, 0为关闭)</label><input type="number" v-model.number="selectedItem.autoplay"></div>
            </div>

            <div v-else-if="selectedItem.type==='grid'">
              <div class="group"><label>列数</label><input type="number" v-model.number="selectedItem.cols" @change="selectedItem.cols = clampNumber(selectedItem.cols,1,6)"></div>
              <div class="group">
                <label>网格项配置</label>
                <div v-for="(item, index) in selectedItem.items" :key="index" class="grid-item-config">
                  <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
                    <span style="font-weight: bold; color: #3f45ff;">网格项 {{ index + 1 }}</span>
                    <button @click="removeGridItem(selectedItem, index)" v-if="selectedItem.items.length > 1" style="padding: 4px 8px; background: #ff6b6b; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;">删除</button>
                  </div>
                  <div class="group grid-2">
                    <div><label>图标</label><input type="text" v-model="item.icon" placeholder="如: photo-o"></div>
                    <div><label>文本</label><input type="text" v-model="item.text"></div>
                  </div>
                  <div class="group"><label>跳转URL</label><input type="text" v-model="item.url" placeholder="可选"></div>
                </div>
                <button @click="addGridItem(selectedItem)" style="margin-top: 8px; padding: 6px 12px; background: #3f45ff; color: white; border: none; border-radius: 4px; cursor: pointer;">+ 添加网格项</button>
              </div>
            </div>

            <div v-else-if="selectedItem.type==='empty'">
              <div class="group"><label>描述</label><input type="text" v-model="selectedItem.text"></div>
            </div>

            <div v-else-if="selectedItem.type==='cell'">
              <div class="group">
                <label>单元格配置</label>
                <div v-for="(cell, index) in selectedItem.cells" :key="index" class="grid-item-config">
                  <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
                    <span style="font-weight: bold; color: #3f45ff;">单元格 {{ index + 1 }}</span>
                    <button @click="removeCellItem(selectedItem, index)" v-if="selectedItem.cells.length > 1" style="padding: 4px 8px; background: #ff6b6b; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;">删除</button>
                  </div>
                  <div class="group grid-2">
                    <div><label>标题</label><input type="text" v-model="cell.title" placeholder="单元格标题"></div>
                    <div><label>图标</label><input type="text" v-model="cell.icon" placeholder="如: shop-o"></div>
                  </div>
                  <div class="group"><label>跳转URL</label><input type="text" v-model="cell.url" placeholder="可选"></div>
                </div>
                <button @click="addCellItem(selectedItem)" style="margin-top: 8px; padding: 6px 12px; background: #3f45ff; color: white; border: none; border-radius: 4px; cursor: pointer;">+ 添加单元格</button>
              </div>
            </div>

            <div v-else-if="selectedItem.type==='loading'">
              <div class="group"><label>类型</label>
                <select v-model="selectedItem.loadingType">
                  <option value="spinner">spinner</option>
                  <option value="circular">circular</option>
                </select>
              </div>
              <div class="group"><label>尺寸(px)</label><input type="text" v-model="selectedItem.size"></div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>

  <script>
  new Vue({
    el:'#app',
    data(){return{
      layout:[],uuid:0,rowHeight:80,selectedId:null,
      basicWidgets:[{type:'card',text:'商品卡片',icon:'📦'},{type:'button',text:'按钮',icon:'🔘'},{type:'image',text:'图片',icon:'🖼️'},{type:'swipe',text:'轮播图',icon:'🎞️'}],
      formWidgets:[{type:'cell',text:'单元格',icon:'📋'},{type:'field',text:'输入框',icon:'⌨️'},{type:'switch',text:'开关',icon:'🔄'}],
      feedbackWidgets:[{type:'countdown',text:'倒计时',icon:'⏱️'},{type:'progress',text:'进度条',icon:'📊'},{type:'loading',text:'加载中',icon:'⚙️'}],
      displayWidgets:[{type:'divider',text:'分割线',icon:'➖'},{type:'tag',text:'标签',icon:'🏷️'},{type:'notice',text:'通知栏',icon:'📢'},{type:'grid',text:'九宫格',icon:'🔲'},{type:'empty',text:'空状态',icon:'📭'},{type:'row',text:'行布局',icon:'📐'}]
    }},
    computed:{
      selectedItem(){return this.layout.find(i=>i.i===this.selectedId)||null;}
    },
    methods:{
      onWidgetDragStart(e,w){e.dataTransfer.setData('widgetType',JSON.stringify(w));},
      handleDrop(e){const data=e.dataTransfer.getData('widgetType');if(!data)return;const w=JSON.parse(data);const rect=this.$refs.gridLayout.getBoundingClientRect();
        const x=Math.floor((e.clientX-rect.left)/(rect.width/12));const y=Math.floor((e.clientY-rect.top)/this.rowHeight);
        const newItem={x,y,w:6,h:4,i:String(this.uuid++),type:w.type,text:w.text};
        if(w.type==='swipe'){newItem.images=['https://img.yzcdn.cn/vant/apple-1.jpg','https://img.yzcdn.cn/vant/apple-2.jpg'];newItem.autoplay=3000;}
        if(w.type==='field'){newItem.label='姓名';newItem.placeholder='请输入姓名';newItem.value='';}
        if(w.type==='switch'){newItem.checked=false;}
        if(w.type==='notice'){newItem.text='欢迎使用 Vant 拖拽生成器';newItem.color='#1989fa';}
        if(w.type==='grid'){newItem.cols=3;newItem.items=[{icon:'photo-o',text:'网格项1',url:''}];}
        if(w.type==='empty'){newItem.text='暂无数据';}
        if(w.type==='loading'){newItem.loadingType='spinner';newItem.size='24px';}
        if(w.type==='cell'){newItem.cells=[{title:'单元格1',icon:'shop-o',url:'https://youzan.github.io/vant'}];}
        this.layout.push(newItem);
        this.selectedId=newItem.i;
      },
      removeItem(id){this.layout=this.layout.filter(i=>i.i!==id);},
      selectItem(id){this.selectedId=id;},
      clearSelection(){this.selectedId=null;},
      clampNumber(v,min,max){v=Number.isFinite(v)?v:0; if(max==null) return Math.max(min,v); return Math.min(Math.max(min,v),max);},
      clampPositionSize(item){
        item.w=this.clampNumber(item.w,1,12);
        item.h=this.clampNumber(item.h,1);
        item.x=this.clampNumber(item.x,0,12-item.w);
        item.y=this.clampNumber(item.y,0);
      },
      onChangeImages(evt,item){const val=evt.target.value||'';item.images=val.split(',').map(s=>s.trim()).filter(Boolean);},
      addGridItem(gridItem){gridItem.items.push({icon:'photo-o',text:'新网格项',url:''});},
      removeGridItem(gridItem,index){gridItem.items.splice(index,1);},
      addCellItem(cellItem){cellItem.cells.push({title:'新单元格',icon:'photo-o',url:''});},
      removeCellItem(cellItem,index){cellItem.cells.splice(index,1);},
      handleKeydown(e){if(e.key==='Delete'&&this.selectedId){if(confirm('确定删除选中组件?')){this.removeItem(this.selectedId);this.selectedId=null;}}},
      saveLayout(){localStorage.setItem('vantBuilderLayout',JSON.stringify(this.layout));alert('已保存');},
      loadLayout(){const d=localStorage.getItem('vantBuilderLayout');if(d)this.layout=JSON.parse(d);else alert('无保存数据');},
      clearLayout(){if(confirm('确定清空?'))this.layout=[];},
      exportJSON(){
        const blob=new Blob([JSON.stringify({layout:this.layout,uuid:this.uuid},null,2)],{type:'application/json'});
        const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='vant-layout.json';a.click();URL.revokeObjectURL(a.href);
      },
      importJSON(e){
        const file=e.target.files[0];if(!file)return;const reader=new FileReader();
        reader.onload=(evt)=>{try{const data=JSON.parse(evt.target.result);this.layout=data.layout||[];this.uuid=data.uuid||0;alert('布局已导入');}catch(err){alert('导入失败');}};
        reader.readAsText(file);
      },
      exportLayout(){alert('此处生成导出 Vue 代码（折叠显示）');}
    }
  });
  </script>
</body>
</html>
